package com.cqrcb.cloud.service.quota;

import com.cqrcb.cloud.config.redis.annotation.CustomCachePut;
import com.cqrcb.cloud.config.redis.annotation.CustomCacheable;
import com.cqrcb.cloud.config.redis.annotation.CustomLock;
import com.cqrcb.cloud.entity.api.req.Q;
import com.cqrcb.cloud.entity.api.res.R;
import com.cqrcb.cloud.entity.constant.MarketConstant;
import com.cqrcb.cloud.entity.constant.ThreadConstant;
import com.cqrcb.cloud.entity.exception.CustomException;
import com.cqrcb.cloud.entity.exception.ExceptionMember;
import com.cqrcb.cloud.entity.quota.CustomerMultiQuotaCtrlDetail;
import com.cqrcb.cloud.entity.quota.CustomerMultiQuotaCtrlMain;
import com.cqrcb.cloud.entity.quota.io.CustomerMultiQuotaCtrlMarketMainIO;
import com.cqrcb.cloud.entity.quota.io.base.QuotaBaseIO;
import com.cqrcb.cloud.mapper.quota.CustomerMultiQuotaCtrlDetailMapper;
import com.cqrcb.cloud.mapper.quota.CustomerMultiQuotaCtrlMainMapper;
import com.cqrcb.cloud.service.quota.base.QuotaBaseServiceImpl;
import com.cqrcb.cloud.service.quota.thread.CustomerDeductionQuotaThread;
import com.cqrcb.cloud.utils.BigDecimalUtils;
import com.cqrcb.cloud.utils.DateUtils;
import com.cqrcb.cloud.utils.SnowFlakeUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
* 扣减用户的额度数据
*
* @author 系统
* @date 2021-06-19 04:39:42
*/
@Slf4j
@Service
public class CustomerMultiQuotaCtrlMainImpl extends QuotaBaseServiceImpl implements CustomerMultiQuotaCtrlMainService {
    @Autowired
    private CustomerMultiQuotaCtrlMainMapper customerMultiQuotaCtrlMarketMainMapper;
    @Autowired
    private CustomerMultiQuotaCtrlDetailMapper customerMultiQuotaCtrlDetailMapper;

    @Autowired
    private CustomerMultiQuotaCtrlMainService customerMultiQuotaCtrlMainService;

    @Autowired
    private CustomerMultiQuotaCtrlDetailService customerMultiQuotaCtrlDetailService;

    @Autowired
    @Qualifier("customerMultiQuotaCtrlMainImpl")
    private QuotaBaseServiceImpl quotaBaseService;

    @Override
    public boolean saveFinancialProductQuota(CustomerMultiQuotaCtrlMain record) {
        return false;
    }

    @Override
    public int updateByPrimaryKeySelective(CustomerMultiQuotaCtrlMain record) {
        return customerMultiQuotaCtrlMarketMainMapper.updateByPrimaryKeySelective(record);
    }

    /**
     * 扣除活动中客户的配额
     * 基于 每天 每周 每月 及总的额度
     *
     * @param recordq 记录
     * @return {@link R}
     */
    @Override
    public R deductionFinancialProductQuota(Q<CustomerMultiQuotaCtrlMarketMainIO> recordq) {
        return deductionFinancialProductQuota("activity", recordq, CustomerMultiQuotaCtrlMarketMainIO.class);
    }

    @Override
    public List<CustomerMultiQuotaCtrlDetail> deductionFinancialProductQuotaCore(QuotaBaseIO record) {
        CustomerMultiQuotaCtrlMarketMainIO record1 = (CustomerMultiQuotaCtrlMarketMainIO) record ;
        List<CustomerMultiQuotaCtrlDetail> ds = customerMultiQuotaCtrlMainService.calcMultiQuotaCtrlMarketMain(record1);
        return ds;
    }

    @Override
    @CustomLock(key = "'quota:user:' + #record.dataType + ':' + #record.dataId + ':' + #record.activityId",waitTime = 5)
    public List<CustomerMultiQuotaCtrlDetail> calcMultiQuotaCtrlMarketMain(CustomerMultiQuotaCtrlMarketMainIO record){
        //获取我现在需要的额度数据
        List<CustomerMultiQuotaCtrlDetail> nowMultiQuotaCtrlMarketDetails = getCustomerMultiQuotaCtrlDetails(record);
        //扣减缓存
        List<CustomerMultiQuotaCtrlDetail> customerMultiQuotaCtrlDetails = customerMultiQuotaCtrlMainService.deductionQuota(record, nowMultiQuotaCtrlMarketDetails);
        //保存到缓存中
        customerMultiQuotaCtrlMainService.saveMultiQuotaCtrlMarketDetailToRedis(record,nowMultiQuotaCtrlMarketDetails);
        //扣减完成之后，将扣减记录异步到数据库，使用mq服务(目前使用线程去更新)
        ThreadConstant.addQueue(new CustomerDeductionQuotaThread(record,quotaBaseService,customerMultiQuotaCtrlMainService));
        return customerMultiQuotaCtrlDetails;
    }

    /**
     * 获取当前用户的额度信息
     *
     * @param record 记录
     * @return {@link List<CustomerMultiQuotaCtrlDetail>}
     */
    private List<CustomerMultiQuotaCtrlDetail> getCustomerMultiQuotaCtrlDetails(CustomerMultiQuotaCtrlMarketMainIO record) {
        //从缓存中获取main
        List<CustomerMultiQuotaCtrlMain> multiQuotaCtrlMains = (List<CustomerMultiQuotaCtrlMain>) quotaBaseService.getMultiQuotaCtrlMarketMainForRedis(record);
        if(CollectionUtils.isEmpty(multiQuotaCtrlMains)){
            throw new CustomException(ExceptionMember.CUSTOMUSER_NOT_QUOTA_NULL_ERROR);
        }
        LinkedHashMap<Long, CustomerMultiQuotaCtrlMain> customerMultiQuotaCtrlMainMaps = multiQuotaCtrlMains
                .stream()
                .collect(Collectors.toMap(CustomerMultiQuotaCtrlMain::getMultiNo, Function.identity(), (key1, key2) -> key2, LinkedHashMap::new));

        //从缓存中获取details
        List<CustomerMultiQuotaCtrlDetail> multiQuotaCtrlMarketDetails = customerMultiQuotaCtrlMainService.getMultiQuotaCtrlMarketDetailForRedis(record);

        //计算时间
        List<CustomerMultiQuotaCtrlDetail> newMultiQuotaCtrlMarketDetails = new ArrayList<>();
        List<CustomerMultiQuotaCtrlDetail> nowMultiQuotaCtrlMarketDetails = new ArrayList<>();
        List<CustomerMultiQuotaCtrlDetail> rmMultiQuotaCtrlMarketDetails = new ArrayList<>();
        calcDateByMain(multiQuotaCtrlMarketDetails, customerMultiQuotaCtrlMainMaps, newMultiQuotaCtrlMarketDetails, nowMultiQuotaCtrlMarketDetails, rmMultiQuotaCtrlMarketDetails);

        //要删除
        customerMultiQuotaCtrlDetailService.deletes(rmMultiQuotaCtrlMarketDetails);

        //要新增
        customerMultiQuotaCtrlDetailService.adds(newMultiQuotaCtrlMarketDetails);

        return nowMultiQuotaCtrlMarketDetails;
    }

    /**
     * 修正缓存
     *
     * @param record 记录
     * @return record
     */
    @Override
    @CustomCachePut(value = "quota",key="'cache:quota:userdetail:' + #record.activityId + ':' + #record.dataType + ':' + #record.dataId")
    public List<CustomerMultiQuotaCtrlDetail> saveMultiQuotaCtrlMarketDetailToRedis(CustomerMultiQuotaCtrlMarketMainIO record,List<CustomerMultiQuotaCtrlDetail> details){
        return details;
    }

    /**
     * 扣除配额，并更新到缓存
     *
     * @param record 记录
     * @return {@link R}
     */
    @Override
    @CustomLock(key = "'quota:user:' + #record.activityId + ':' + #record.dataType + ':' + #record.dataId",waitTime = 5,leaseTime = 300)
    public List<CustomerMultiQuotaCtrlDetail> deductionQuota(CustomerMultiQuotaCtrlMarketMainIO record,List<CustomerMultiQuotaCtrlDetail> nowMultiQuotaCtrlMarketDetails){
        if(nowMultiQuotaCtrlMarketDetails == null){
            throw new CustomException(ExceptionMember.CUSTOMUSER_NOT_QUOTA_NULL_ERROR);
        }
        String operationType = record.getOperationType();
        BigDecimal useQuota = record.getUseQuota();
        //此乃锁定额度
        //得到剩余额度
        for (CustomerMultiQuotaCtrlDetail nowMultiQuotaCtrlMarketDetail : nowMultiQuotaCtrlMarketDetails) {
            BigDecimal receivedQuota = BigDecimalUtils.defZero(nowMultiQuotaCtrlMarketDetail.getTotalQuota());
            BigDecimal lockQuota = BigDecimalUtils.defZero(nowMultiQuotaCtrlMarketDetail.getLockTotalQuota());
            BigDecimal usedQuota = BigDecimalUtils.defZero(nowMultiQuotaCtrlMarketDetail.getUsedTotalQuota());
            if(receivedQuota == null || BigDecimalUtils.compareToZero(receivedQuota)){
                throw new CustomException(ExceptionMember.REST_QUOTA_ERROR);
            }
            //1锁定额度 2直接扣减
            if(MarketConstant.OPERATION_TYPE_1.equals(operationType)
                    || MarketConstant.OPERATION_TYPE_2.equals(operationType)){
                BigDecimal add = BigDecimalUtils.adds(lockQuota,usedQuota,useQuota);
                //如果目前的已锁定额度 + 已使用额度 + 即将使用的额度 > 总额度 ，则直接报错
                if(BigDecimalUtils.compareTo(add, receivedQuota) == 1){
                    throw new CustomException(ExceptionMember.REST_QUOTA_Not_ENOUGH_ERROR);
                }
            }
            //1锁定额度
            if(MarketConstant.OPERATION_TYPE_1.equals(operationType)){
                lockQuota = BigDecimalUtils.add(lockQuota, useQuota);
                nowMultiQuotaCtrlMarketDetail.setLockTotalQuota(lockQuota);
                //2扣减额度
            }else if(MarketConstant.OPERATION_TYPE_2.equals(operationType)){
                usedQuota = BigDecimalUtils.add(usedQuota, useQuota);
                nowMultiQuotaCtrlMarketDetail.setUsedTotalQuota(usedQuota);
                //3回冲锁定额度
            }else if(MarketConstant.OPERATION_TYPE_3.equals(operationType)){
                lockQuota = BigDecimalUtils.sub(lockQuota, useQuota);
                if(BigDecimalUtils.compareTo(BigDecimal.ZERO, lockQuota) == 1){
                    throw new CustomException(ExceptionMember.RECHARGE_QUOTA_ERROR_3);
                }
                nowMultiQuotaCtrlMarketDetail.setLockTotalQuota(lockQuota);
                //4回冲扣减额度
            }else if(MarketConstant.OPERATION_TYPE_4.equals(operationType)){
                usedQuota = BigDecimalUtils.sub(usedQuota, useQuota);
                if(BigDecimalUtils.compareTo(BigDecimal.ZERO, usedQuota) == 1){
                    throw new CustomException(ExceptionMember.RECHARGE_QUOTA_ERROR_4);
                }
                nowMultiQuotaCtrlMarketDetail.setUsedTotalQuota(usedQuota);
            }
        }
        return nowMultiQuotaCtrlMarketDetails;
    }

    /**
     * 从缓存中获取额度信息，如果额度不存在，则从数据库中获取
     *
     * @param record 记录
     * @return boolean
     */
    @Override
    @CustomCacheable(value = "quota",key="'cache:quota:userdetail:' + #record.activityId + ':' + #record.dataType + ':' + #record.dataId")
    public List<CustomerMultiQuotaCtrlDetail> getMultiQuotaCtrlMarketDetailForRedis(CustomerMultiQuotaCtrlMarketMainIO record){
        //当进入这个方法时，则表示当前的额度信息在缓存中不存在
        return customerMultiQuotaCtrlMainService.getMultiQuotaCtrlMarketDetailForDB(record);
    }




    /**
     * 计算时间的主方法
     *
     * @param multiQuotaCtrlMarketDetails    当前的额度详情
     * @param multiQuotaCtrlMainMaps         当前的主额度信息
     * @param newMultiQuotaCtrlMarketDetails 需要插入到数据库的数据
     * @param nowMultiQuotaCtrlMarketDetails 从新计算后需要用到的额度信息
     * @param rmMultiQuotaCtrlMarketDetails  需要删除的额度数据
     */
    private void calcDateByMain(
                        List<CustomerMultiQuotaCtrlDetail> multiQuotaCtrlMarketDetails,
                        LinkedHashMap<Long, CustomerMultiQuotaCtrlMain> multiQuotaCtrlMainMaps,
                        List<CustomerMultiQuotaCtrlDetail> newMultiQuotaCtrlMarketDetails,
                        List<CustomerMultiQuotaCtrlDetail> nowMultiQuotaCtrlMarketDetails,
                        List<CustomerMultiQuotaCtrlDetail> rmMultiQuotaCtrlMarketDetails) {

        //计算时间
        for (CustomerMultiQuotaCtrlDetail d : multiQuotaCtrlMarketDetails) {
            Date beginTime = d.getBeginTime();
            Date endTime = d.getEndTime();
            long multiNo = d.getMultiNo();

            //如果详情表中的数据在主表中并不存在，则准备删除详情表相关的数据
            if(!multiQuotaCtrlMainMaps.containsKey(multiNo)){
                rmMultiQuotaCtrlMarketDetails.add(d);
                continue;
            }
            CustomerMultiQuotaCtrlMain m = multiQuotaCtrlMainMaps.get(multiNo);
            String timeType = m.getTimeType();
            BigDecimal timeData = m.getTimeData();
            //如果主表中的时间类型为空，则准备删除详情表相关的数据
            if(StringUtils.isEmpty(timeType) || StringUtils.isEmpty(timeData)){
                rmMultiQuotaCtrlMarketDetails.add(d);
                continue;
            }
            //判断当前日期是否在指定的日期中间
            boolean flag = DateUtils.dateIntervalEquals(beginTime, endTime);
            if(!flag){
                buildDetailList(newMultiQuotaCtrlMarketDetails, nowMultiQuotaCtrlMarketDetails, m);
            }else{
                nowMultiQuotaCtrlMarketDetails.add(d);
            }
            //通过以上逻辑，则说明主表中的数据在详情表中存在，则可以将主表map中的数据删除
            multiQuotaCtrlMainMaps.remove(multiNo);
        }

        //如果主表中还有剩余的数据，则表示详情表中没有对应的数据，则需要生成
        if(!CollectionUtils.isEmpty(multiQuotaCtrlMainMaps)){
            multiQuotaCtrlMainMaps.forEach((k,m)->{
                buildDetailList(newMultiQuotaCtrlMarketDetails, nowMultiQuotaCtrlMarketDetails, m);
            });
        }
    }

    /**
     * 生成额度详情数据
     *
     * @param newMultiQuotaCtrlMarketDetails 新的多配额ctrl市场细节
     * @param nowMultiQuotaCtrlMarketDetails 现在多配额ctrl市场细节
     * @param m                              米
     */
    private void buildDetailList(List<CustomerMultiQuotaCtrlDetail> newMultiQuotaCtrlMarketDetails,
                                 List<CustomerMultiQuotaCtrlDetail> nowMultiQuotaCtrlMarketDetails,
                                 CustomerMultiQuotaCtrlMain m) {
        CalcDate calcDate = new CalcDate(m).invoke();
        Date xbeginTime = calcDate.getXbeginTime();
        Date xendTime = calcDate.getXendTime();
        CustomerMultiQuotaCtrlDetail newd = bulidQuotaDetail(m, xbeginTime, xendTime);
        newMultiQuotaCtrlMarketDetails.add(newd);
        nowMultiQuotaCtrlMarketDetails.add(newd);
    }


    /**
     * 废弃的方法
     * 计算日期
     *
     * @param multiQuotaCtrlMains            主表数据
     * @param multiQuotaCtrlDetailMaps       详情表数据
     * @param newMultiQuotaCtrlMarketDetails 需要新增到详情表的数据
     * @param nowMultiQuotaCtrlMarketDetails 现在详情表的数据
     * @param rmMultiQuotaCtrlMarketDetails  需要从详情表删除的数据
     */
    @Deprecated
    private void calcDate(List<CustomerMultiQuotaCtrlMain> multiQuotaCtrlMains,
                          LinkedHashMap<Long, CustomerMultiQuotaCtrlDetail> multiQuotaCtrlDetailMaps,
                          List<CustomerMultiQuotaCtrlDetail> newMultiQuotaCtrlMarketDetails,
                          List<CustomerMultiQuotaCtrlDetail> nowMultiQuotaCtrlMarketDetails,
                          List<CustomerMultiQuotaCtrlDetail> rmMultiQuotaCtrlMarketDetails) {
        //计算时间
        for (CustomerMultiQuotaCtrlMain m : multiQuotaCtrlMains) {
            String timeType = m.getTimeType();
            BigDecimal timeData = m.getTimeData();
            Date beginTime = m.getBeginTime();
            Date endTime = m.getEndTime();
            long multiNo = m.getMultiNo();

            Date xbeginTime = null;
            Date xendTime = null;
            if(MarketConstant.USER_QUOTA_DAY_1.equals(timeType)){
                //每天 计算出开始时间和结束时间
                xbeginTime = DateUtils.getDayFirstDateBegin();
                xendTime = DateUtils.getDayLastDateEnd();
            }else if(MarketConstant.USER_QUOTA_WEEK_1.equals(timeType)){
                //每周 计算出开始时间和结束时间
                xbeginTime = DateUtils.getWeekFirstDateBeginCompare(beginTime);
                xendTime = DateUtils.getWeekLastDateEndCompare(endTime);
            }else if(MarketConstant.USER_QUOTA_MONTH_1.equals(timeType)){
                //每月
                xbeginTime = DateUtils.getMonthFirstDateBeginCompare(beginTime);
                xendTime = DateUtils.getMonthLastDateEndCompare(endTime);
            }else if(MarketConstant.USER_QUOTA_ALL.equals(timeType)){
                //整个活动期间
                xbeginTime = beginTime;
                xendTime = endTime;
            }
            if(multiQuotaCtrlDetailMaps.containsKey(multiNo)){
                CustomerMultiQuotaCtrlDetail customerMultiQuotaCtrlDetail = multiQuotaCtrlDetailMaps.get(multiNo);
                Date dbeginTime = customerMultiQuotaCtrlDetail.getBeginTime();
                Date dendTime = customerMultiQuotaCtrlDetail.getEndTime();
                //两个时间相等
                if(DateUtils.dateEquals(dbeginTime, xbeginTime) && DateUtils.dateEquals(dendTime, xendTime)){
                    nowMultiQuotaCtrlMarketDetails.add(customerMultiQuotaCtrlDetail);
                }else{
                    //两个时间不相等 需要删除
                    //然后重新创建
                    rmMultiQuotaCtrlMarketDetails.add(customerMultiQuotaCtrlDetail);
                }
            }else{
                //需要生成detail数据
                CustomerMultiQuotaCtrlDetail d = bulidQuotaDetail(m,xbeginTime,xendTime);
                newMultiQuotaCtrlMarketDetails.add(d);
                nowMultiQuotaCtrlMarketDetails.add(d);
            }
        }
    }

    /**
     * 生成配额详细信息
     *
     * @param m          米
     * @param xbeginTime xbegin时间
     * @param xendTime   xend时间
     * @return {@link CustomerMultiQuotaCtrlDetail}
     */
    private CustomerMultiQuotaCtrlDetail bulidQuotaDetail(CustomerMultiQuotaCtrlMain m,Date xbeginTime,Date xendTime) {
        CustomerMultiQuotaCtrlDetail d = new CustomerMultiQuotaCtrlDetail();
        BeanUtils.copyProperties(m, d);
        d.setMultiNo(m.getMultiNo());
        d.setMultiDetailNo(SnowFlakeUtils.getId());
        d.setBeginTime(xbeginTime);
        d.setEndTime(xendTime);
        return d;
    }


    /**
     * 从redis中获取数据
     * @param record 记录
     * @return {@link List<CustomerMultiQuotaCtrlMain>}
     */
    @Override
    @CustomCacheable(value = "quota",key="'cache:quota:usermain:' + #record.dataType + ':' + #record.dataId + ':' + #record.activityId")
    public List<CustomerMultiQuotaCtrlMain> getMultiQuotaCtrlMarketMainForRedis(QuotaBaseIO record) {
        CustomerMultiQuotaCtrlMarketMainIO record1 = (CustomerMultiQuotaCtrlMarketMainIO) record ;
        List<CustomerMultiQuotaCtrlMain> multiQuotaCtrlMarketMain = customerMultiQuotaCtrlMainService.getMultiQuotaCtrlMarketMainForDB(record1);
        return multiQuotaCtrlMarketMain;
    }


    @Override
    @CustomLock(key = "'quota:usermain:' + #record.dataType + ':' + #record.dataId + ':' + #record.activityId",isFailFast = true)
    public List<CustomerMultiQuotaCtrlMain> getMultiQuotaCtrlMarketMainForDB(CustomerMultiQuotaCtrlMarketMainIO record){
        //当进入这个方法时，则表示当前的额度信息在缓存中不存在
        return customerMultiQuotaCtrlMarketMainMapper.selectByIO(record);
    }

    /**
     * 从数据库中获取额度数据，拦截其他请求，使其他请求快速失败
     *
     * @param record 记录
     * @return boolean
     */
    @Override
    @CustomLock(key = "'quota:userdetail:' + #record.dataType + ':' + #record.dataId + ':' + #record.activityId",isFailFast = true)
    public List<CustomerMultiQuotaCtrlDetail> getMultiQuotaCtrlMarketDetailForDB(CustomerMultiQuotaCtrlMarketMainIO record){
        List<CustomerMultiQuotaCtrlDetail> customerMultiQuotaCtrlDetails = customerMultiQuotaCtrlDetailMapper.selectByIO(record);
        return customerMultiQuotaCtrlDetails;
    }

    @Override
    @CustomLock(key = "'quota:updateuserdetail:' + #record.dataType + ':' + #record.dataId + ':' + #record.activityId",isFailFast = true)
    public int updateMultiQuotaCtrlMarketDetailToDB(CustomerMultiQuotaCtrlMarketMainIO record,List<CustomerMultiQuotaCtrlDetail> customerMultiQuotaCtrlDetails){
        return customerMultiQuotaCtrlDetailService.updates(customerMultiQuotaCtrlDetails);
    }


    /**
     * 计算额度的内部类
     *
     * @author yangxueyong
     * @date 2021/06/25
     */
    private class CalcDate {
        private CustomerMultiQuotaCtrlMain m;
        private Date xbeginTime;
        private Date xendTime;

        public CalcDate(CustomerMultiQuotaCtrlMain m) {
            this.m = m;
        }

        public Date getXbeginTime() {
            return xbeginTime;
        }

        public Date getXendTime() {
            return xendTime;
        }

        public CalcDate invoke() {
            BigDecimal timeData = m.getTimeData();
            String timeType = m.getTimeType();
            int num = timeData.intValue();
            Date mbeginTime = m.getBeginTime();
            Date mendTime = m.getEndTime();

            xbeginTime = null;
            xendTime = null;
            if(MarketConstant.USER_QUOTA_DAY.equals(timeType)){
                xbeginTime = DateUtils.getDayFirstDateBegin();
                xendTime = DateUtils.getDayLastDateEndCompare(num,mendTime);
            }else  if(MarketConstant.USER_QUOTA_WEEK.equals(timeType)){
                xbeginTime = DateUtils.getWeekFirstDate();
                xendTime = DateUtils.getWeekLastDateEndCompare(num,mendTime);
            }else  if(MarketConstant.USER_QUOTA_MONTH.equals(timeType)){
                xbeginTime = DateUtils.getMonthFirstDate();
                xendTime = DateUtils.getMonthLastDateEndCompare(num,mendTime);
            }else  if(MarketConstant.USER_QUOTA_ALL.equals(timeType)){
                xbeginTime = mbeginTime;
                xendTime = mendTime;
            }
            return this;
        }
    }
}
